package docker

import (
	"bytes"
	"context"
	"io"
	"strconv"
	"strings"

	"github.com/cuigh/swirl/misc"
	"github.com/cuigh/swirl/model"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/client"
	"github.com/docker/docker/pkg/stdcopy"
)

// ContainerList return containers on the host.
func ContainerList(args *model.ContainerListArgs) (infos []*model.ContainerListInfo, totalCount int, err error) {
	err = mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
		var (
			containers []types.Container
			opts       = types.ContainerListOptions{Filters: filters.NewArgs()}
		)

		if args.Filter == "" {
			opts.All = true
		} else {
			opts.Filters.Add("status", args.Filter)
		}
		if args.Name != "" {
			opts.Filters.Add("name", args.Name)
		}

		containers, err = cli.ContainerList(ctx, opts)
		if err == nil {
			//sort.Slice(containers, func(i, j int) bool {
			//	return containers[i] < containers[j].Description.Hostname
			//})
			totalCount = len(containers)
			start, end := misc.Page(totalCount, args.PageIndex, args.PageSize)
			containers = containers[start:end]
			if length := len(containers); length > 0 {
				infos = make([]*model.ContainerListInfo, length)
				for i, c := range containers {
					infos[i] = model.NewContainerListInfo(c)
				}
			}
		}
		return
	})
	return
}

// ContainerInspect return detail information of a container.
func ContainerInspect(id string) (container types.ContainerJSON, err error) {
	err = mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
		container, err = cli.ContainerInspect(ctx, id)
		return
	})
	return
}

// ContainerInspectRaw return container raw information.
func ContainerInspectRaw(id string) (container types.ContainerJSON, raw []byte, err error) {
	var (
		ctx context.Context
		cli *client.Client
	)
	if ctx, cli, err = mgr.Client(); err == nil {
		container, raw, err = cli.ContainerInspectWithRaw(ctx, id, true)
	}
	return
}

// ContainerRemove remove a container.
func ContainerRemove(id string) error {
	return mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
		opts := types.ContainerRemoveOptions{}
		err = cli.ContainerRemove(ctx, id, opts)
		return
	})
}

// ContainerLogs returns the logs generated by a container.
func ContainerLogs(id string, line int, timestamps bool) (stdout, stderr *bytes.Buffer, err error) {
	var (
		ctx context.Context
		cli *client.Client
		rc  io.ReadCloser
	)

	ctx, cli, err = mgr.Client()
	if err != nil {
		return
	}

	opts := types.ContainerLogsOptions{
		ShowStdout: true,
		ShowStderr: true,
		Tail:       strconv.Itoa(line),
		Timestamps: timestamps,
		//Since: (time.Hour * 24).String()
	}
	if rc, err = cli.ContainerLogs(ctx, id, opts); err == nil {
		defer rc.Close()

		stdout = &bytes.Buffer{}
		stderr = &bytes.Buffer{}
		_, err = stdcopy.StdCopy(stdout, stderr, rc)
	}
	return
}

// ContainerExecCreate creates an exec instance.
func ContainerExecCreate(id string, cmd string) (resp types.IDResponse, err error) {
	err = mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
		opts := types.ExecConfig{
			AttachStdin:  true,
			AttachStdout: true,
			AttachStderr: true,
			Tty:          true,
			//User: "root",
			Cmd: strings.Split(cmd, " "),
		}
		//cli.DialSession()
		resp, err = cli.ContainerExecCreate(ctx, id, opts)
		return
	})
	return
}

// ContainerExecAttach attaches a connection to an exec process in the server.
func ContainerExecAttach(id string) (resp types.HijackedResponse, err error) {
	err = mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
		opts := types.ExecStartCheck{
			Detach: false,
			Tty:    true,
		}
		resp, err = cli.ContainerExecAttach(ctx, id, opts)
		return err
	})
	return
}

// ContainerExecStart starts an exec instance.
func ContainerExecStart(id string) error {
	return mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
		opts := types.ExecStartCheck{
			Detach: false,
			Tty:    true,
		}
		return cli.ContainerExecStart(ctx, id, opts)
	})
}
